// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/ozone/evdev/input_injector_evdev.h"

#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
#include "ui/events/ozone/evdev/event_converter_test_util.h"
#include "ui/events/ozone/evdev/event_factory_evdev.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"

namespace ui {

using testing::AllOf;
using testing::InSequence;
using testing::Property;

class EventObserver {
public:
    void EventDispatchCallback(Event* event)
    {
        DispatchEventFromNativeUiEvent(
            event, base::Bind(&EventObserver::OnEvent, base::Unretained(this)));
    }

    void OnEvent(Event* event)
    {
        if (event->IsMouseEvent()) {
            if (event->type() == ET_MOUSEWHEEL) {
                OnMouseWheelEvent(static_cast<MouseWheelEvent*>(event));
            } else {
                OnMouseEvent(static_cast<MouseEvent*>(event));
            }
        }
    }

    // Mock functions for intercepting mouse events.
    MOCK_METHOD1(OnMouseEvent, void(MouseEvent* event));
    MOCK_METHOD1(OnMouseWheelEvent, void(MouseWheelEvent* event));
};

class MockCursorEvdev : public CursorDelegateEvdev {
public:
    MockCursorEvdev() { }
    ~MockCursorEvdev() override { }

    // CursorDelegateEvdev:
    void MoveCursorTo(gfx::AcceleratedWidget widget,
        const gfx::PointF& location) override
    {
        cursor_location_ = location;
    }
    void MoveCursorTo(const gfx::PointF& location) override
    {
        cursor_location_ = location;
    }
    void MoveCursor(const gfx::Vector2dF& delta) override
    {
        cursor_location_ = gfx::PointF(delta.x(), delta.y());
    }
    bool IsCursorVisible() override { return 1; }
    gfx::Rect GetCursorConfinedBounds() override
    {
        NOTIMPLEMENTED();
        return gfx::Rect();
    }
    gfx::PointF GetLocation() override { return cursor_location_; }

private:
    // The location of the mock cursor.
    gfx::PointF cursor_location_;

    DISALLOW_COPY_AND_ASSIGN(MockCursorEvdev);
};

MATCHER_P4(MatchesMouseEvent, type, button, x, y, "")
{
    if (arg->type() != type) {
        *result_listener << "Expected type: " << type << " actual: " << arg->type()
                         << " (" << arg->name() << ")";
        return false;
    }
    if (button == EF_LEFT_MOUSE_BUTTON && !arg->IsLeftMouseButton()) {
        *result_listener << "Expected the left button flag is set.";
        return false;
    }
    if (button == EF_RIGHT_MOUSE_BUTTON && !arg->IsRightMouseButton()) {
        *result_listener << "Expected the right button flag is set.";
        return false;
    }
    if (button == EF_MIDDLE_MOUSE_BUTTON && !arg->IsMiddleMouseButton()) {
        *result_listener << "Expected the middle button flag is set.";
        return false;
    }
    if (arg->x() != x || arg->y() != y) {
        *result_listener << "Expected location: (" << x << ", " << y
                         << ") actual: (" << arg->x() << ", " << arg->y() << ")";
        return false;
    }
    return true;
}

class InputInjectorEvdevTest : public testing::Test {
public:
    InputInjectorEvdevTest();

protected:
    void SimulateMouseClick(int x, int y, EventFlags button, int count);
    void ExpectClick(int x, int y, int button, int count);

    EventObserver event_observer_;
    EventDispatchCallback dispatch_callback_;
    MockCursorEvdev cursor_;

    scoped_ptr<DeviceManager> device_manager_;
    scoped_ptr<EventFactoryEvdev> event_factory_;

    InputInjectorEvdev injector_;

    base::MessageLoop message_loop_;
    base::RunLoop run_loop_;

private:
    DISALLOW_COPY_AND_ASSIGN(InputInjectorEvdevTest);
};

InputInjectorEvdevTest::InputInjectorEvdevTest()
    : dispatch_callback_(base::Bind(&EventObserver::EventDispatchCallback,
        base::Unretained(&event_observer_)))
    , device_manager_(CreateDeviceManagerForTest())
    , event_factory_(CreateEventFactoryEvdevForTest(
          &cursor_,
          device_manager_.get(),
          ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine(),
          dispatch_callback_))
    , injector_(CreateDeviceEventDispatcherEvdevForTest(event_factory_.get()),
          &cursor_)
{
}

void InputInjectorEvdevTest::SimulateMouseClick(int x,
    int y,
    EventFlags button,
    int count)
{
    injector_.MoveCursorTo(gfx::PointF(x, y));
    for (int i = 0; i < count; i++) {
        injector_.InjectMouseButton(button, true);
        injector_.InjectMouseButton(button, false);
    }
}

void InputInjectorEvdevTest::ExpectClick(int x, int y, int button, int count)
{
    InSequence dummy;
    EXPECT_CALL(event_observer_,
        OnMouseEvent(MatchesMouseEvent(ET_MOUSE_MOVED, EF_NONE, x, y)));

    for (int i = 0; i < count; i++) {
        EXPECT_CALL(event_observer_, OnMouseEvent(MatchesMouseEvent(ET_MOUSE_PRESSED, button, x, y)));
        EXPECT_CALL(event_observer_, OnMouseEvent(MatchesMouseEvent(ET_MOUSE_RELEASED, button, x, y)));
    }
}

TEST_F(InputInjectorEvdevTest, LeftClick)
{
    ExpectClick(12, 13, EF_LEFT_MOUSE_BUTTON, 1);
    SimulateMouseClick(12, 13, EF_LEFT_MOUSE_BUTTON, 1);
    run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, RightClick)
{
    ExpectClick(12, 13, EF_RIGHT_MOUSE_BUTTON, 1);
    SimulateMouseClick(12, 13, EF_RIGHT_MOUSE_BUTTON, 1);
    run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, DoubleClick)
{
    ExpectClick(12, 13, EF_LEFT_MOUSE_BUTTON, 2);
    SimulateMouseClick(12, 13, EF_LEFT_MOUSE_BUTTON, 2);
    run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, MouseMoved)
{
    injector_.MoveCursorTo(gfx::PointF(1, 1));
    run_loop_.RunUntilIdle();
    EXPECT_EQ(cursor_.GetLocation(), gfx::PointF(1, 1));
}

TEST_F(InputInjectorEvdevTest, MouseDragged)
{
    InSequence dummy;
    EXPECT_CALL(event_observer_,
        OnMouseEvent(MatchesMouseEvent(ET_MOUSE_PRESSED,
            EF_LEFT_MOUSE_BUTTON, 0, 0)));
    EXPECT_CALL(event_observer_,
        OnMouseEvent(MatchesMouseEvent(ET_MOUSE_DRAGGED,
            EF_LEFT_MOUSE_BUTTON, 1, 1)));
    EXPECT_CALL(event_observer_,
        OnMouseEvent(MatchesMouseEvent(ET_MOUSE_DRAGGED,
            EF_LEFT_MOUSE_BUTTON, 2, 3)));
    EXPECT_CALL(event_observer_,
        OnMouseEvent(MatchesMouseEvent(ET_MOUSE_RELEASED,
            EF_LEFT_MOUSE_BUTTON, 2, 3)));
    injector_.InjectMouseButton(EF_LEFT_MOUSE_BUTTON, true);
    injector_.MoveCursorTo(gfx::PointF(1, 1));
    injector_.MoveCursorTo(gfx::PointF(2, 3));
    injector_.InjectMouseButton(EF_LEFT_MOUSE_BUTTON, false);
    run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, MouseWheel)
{
    InSequence dummy;
    EXPECT_CALL(event_observer_, OnMouseWheelEvent(AllOf(MatchesMouseEvent(ET_MOUSEWHEEL, 0, 10, 20), Property(&MouseWheelEvent::x_offset, 0), Property(&MouseWheelEvent::y_offset, 100))));
    EXPECT_CALL(event_observer_, OnMouseWheelEvent(AllOf(MatchesMouseEvent(ET_MOUSEWHEEL, 0, 10, 20), Property(&MouseWheelEvent::x_offset, 100), Property(&MouseWheelEvent::y_offset, 0))));
    injector_.MoveCursorTo(gfx::PointF(10, 20));
    injector_.InjectMouseWheel(0, 100);
    injector_.InjectMouseWheel(100, 0);
    run_loop_.RunUntilIdle();
}

} // namespace ui
